/*
  Ejemplo de programa servidor. Atiende peticiones aritmeticas, el protocolo de transporte sera HTTP
  ver http::Transport y el contenido del mensaje sera el resutaldo de un comm::Codec con los
  (x, y, op) -> El resultado sera estos tres componente mas result.

  Para poder probar el sistema de congestion se puede indicar un numero de milisegundos de
  retardo aplicados a cada contestacion.
  
  Ejemplo de implementacion usando el sistema iRS de NemesisRD.comm 1.5.2
  

  Los clientes pueden ser: https_kclient.p https_client.p
*/
#include <iostream>

#include <nemesis.h>

#include <nemesis.xml.Node.h>
#include <nemesis.xml.Attribute.h>

//#include <nemesis.comm.h>

#include <nemesis.comm.ClientSocket.h>
#include <nemesis.comm.Application.h>
#include <nemesis.comm.ReceiverFactoryImpl.h>
#include <nemesis.comm.Network.h>
#include <nemesis.comm.Device.h>
#include <nemesis.comm.CongestionController.h>

#include <nemesis.commsec.functions.h>
#include <nemesis.commsec.ServerSocket.h>

#include <nemesis.http.Request.h>
#include <nemesis.http.Response.h>
#include <nemesis.http.Handler.h>
#include <nemesis.http.Transport.h>
#include <nemesis.http.functions.h>

#include <test.Request.h>
#include <test.Response.h>
#include <test.Communicator.h>

using namespace std;

class MyCommunicator : public test::Communicator {
public:
   MyCommunicator () {;}
};

class MyHandler : public http::Handler {
public:
   MyHandler () : http::Handler ("https_rserver::MyHandler") {;}

   static const char* className () throw () { return "https_rserver::ReceiverFactory"; }

private:
   MyCommunicator* a_communicator;
   test::Request a_testRequest;
   test::Response a_testResponse;

   void initialize () throw (RuntimeException);
   void evRequest (comm::ClientSocket&, const http::Request& request) throw (RuntimeException);
   void evResponse (comm::ClientSocket&, const http::Response&) throw (RuntimeException) {;}
};

class HTTPArithmeticServer : public comm::Application {
public:
   HTTPArithmeticServer ();
      
private:
   MyCommunicator a_communicator;
   comm::ReceiverFactoryImpl <MyHandler> a_receiverFactory;
   commsec::ServerSocket* a_serverSocket;

   void initialize () throw (RuntimeException);
   void run () throw (RuntimeException);
   xml::Node* asXML (xml::Node* app) const throw ();
};

using namespace std;
using namespace nemesis::comm;

int main (int argc, const char** argv)
{
   CommandLine& commandLine (CommandLine::instanciate ());
   HTTPArithmeticServer app;
   TraceWriter* tw;

   srand (time (NULL));

   try {
      commandLine.initialize (argv, argc);
      commandLine.verify ();

      Logger::setLevel (Logger::Debug); 
      Logger::initialize ("https_server", tw = new nemesis::TraceWriter ("file.trace", 4048000));

      commsec::functions::initialize (tw); 
      http::functions::initialize ();

      app.start ();
   }
   catch (Exception& ex) {
      cout << ex.asString () << endl;
   }
   
   return 0;
}

HTTPArithmeticServer::HTTPArithmeticServer () : 
   Application ("https_rserver", "Servidor HTTPS de operaciones aritmeticas (iRS)", "1.0") 
{
   CommandLine& commandLine (CommandLine::instanciate ());

   commandLine.add ("cert", CommandLine::Argument::Mandatory, "Archivo que contiene el certificado");
   commandLine.add ("key", CommandLine::Argument::Mandatory, "Archivo que contiene la clave privada.");
   commandLine.add ("p", CommandLine::Argument::Mandatory, "Puerto en el que atender peticiones");
   commandLine.add ("a", CommandLine::Argument::Mandatory, "Direccin IP en la que atender");
   commandLine.add ("d", CommandLine::Argument::Mandatory, "Retardo aplicado a la contestacion");
   commandLine.add ("r", CommandLine::Argument::Optional, "Indicador de reuso de direccin", false);
   commandLine.add ("n", CommandLine::Argument::Optional, "Numero de mensajes a servir", true);
   commandLine.add ("trace", CommandLine::Argument::Optional, "Nivel de trazas (debug,warning, error,...)");
}

void HTTPArithmeticServer::initialize () 
   throw (RuntimeException)
{
   CommandLine& cl (CommandLine::instanciate ());

   int port = cl.getIntegerValue ("p");
   const comm::Device* device = comm::Network::instanciate ().find (comm::Device::asAddress (cl.getValue ("a")));

   a_serverSocket = new commsec::ServerSocket (
      commsec::Method::SSLv23, cl.getValue ("cert"), cl.getValue ("key"),
      INetAddress (device, port), cl.exists ("r"), &http::Transport::getFactory ()
   );
   a_serverSocket->setReceiverFactory (a_receiverFactory);
}

void HTTPArithmeticServer::run ()
   throw (RuntimeException)
{
   CommandLine& cl (CommandLine::instanciate ());

   a_communicator.attach (a_serverSocket);
   a_communicator.setDelay (cl.getIntegerValue ("d"));

   if (cl.exists ("n") == true)
      a_communicator.setMaxMessage (cl.getIntegerValue ("n"));

   if (cl.exists ("trace"))
      Logger::setLevel (Logger::asLevel (cl.getValue ("trace")));

   a_communicator.accept ();
}

xml::Node* HTTPArithmeticServer::asXML (xml::Node* app) const 
   throw ()
{
   xml::Node* node = app::Application::asXML (app);
   
   node->createAttribute ("MaxMessage", a_communicator.getMaxMessage ());
   node->createAttribute ("Message", a_communicator.getMessage ());
   
   return node;
}

void MyHandler::initialize ()
   throw (RuntimeException)
{
   a_communicator = app::functions::component <MyCommunicator> (FILE_LOCATION);
   allocateResponse ()->createHeader (http::Header::Type::Date); 
}

void MyHandler::evRequest (comm::ClientSocket& clientSocket, const http::Request& request)
   throw (RuntimeException)
{
   LOGMETHOD (TraceMethod tm ("MyReceiver", "apply", FILE_LOCATION));

   if (a_communicator->canContinue (clientSocket) == false)
      return;      
      
   const DataBlock& body = request.getBody ();

   if (body.getSize () == 0)
      throw RuntimeException ("La peticion no incorpora los parametros de operacion", FILE_LOCATION);

   LOGINFORMATION (
      string msg ("Body recibido: ");
      msg += nemesis::functions::asString (body);
      Logger::information (msg, FILE_LOCATION);
   );

   if (Codec::getType (body) != test::Request::Id) 
      throw RuntimeException ("El mensaje recibido no es una peticion aritmetica", FILE_LOCATION);

   a_testRequest.decode (body);
   
   a_communicator->delay ();

   a_testResponse.x = a_testRequest.x;
   a_testResponse.y = a_testRequest.y;
   a_testResponse.initTime = a_testRequest.initTime;
   
   http::Response* response = allocateResponse ();

   response->setStatusCode (200);

   switch (a_testResponse.op = a_testRequest.op) {
      case '+':
         a_testResponse.result = a_testRequest.x + a_testRequest.y;
         break;
      case '-':
         a_testResponse.result = a_testRequest.x - a_testRequest.y;
         break;
      case '*':
         a_testResponse.result = a_testRequest.x * a_testRequest.y;
         break;
      case '/':
         if (a_testRequest.y == 0) {
            response->setStatusCode (400);
            response->setReasonPhrase ("Division por cero");
            a_testResponse.result = 0;
         }
         else
            a_testResponse.result = a_testRequest.x / a_testRequest.y;
         break;
   }

   response->setBody (a_testResponse.code ());      

   response->find (http::Header::Type::Date)->setValue ("Mon, 30 Jan 2006 14:36:18 GMT");
   
   http::Header* keepAlive = response->find ("Keep-Alive");
   
   if (keepAlive == NULL)
      keepAlive = response->createHeader ("Keep-Alive");   
   
   keepAlive->setValue ("Verificacion del cambio 1.0.7");      

   LOGINFORMATION (
      string msg = nemesis::functions::asString ("%d %c %d = %d", a_testRequest.x, a_testRequest.op, a_testRequest.y, a_testResponse.result);
      Logger::information (msg, FILE_LOCATION);
   );

   try {
      clientSocket.send (*response);
   }
   catch (Exception& ex) {
      ex.trace ();
   }
}

